﻿#include "common.h"
// C/C++ 学习网站：https://legacy.cplusplus.com 注意旧版本（搜索）和新版本

//动态内存管理
/* 动态内存管理函数主要有：
    1. malloc()：申请动态内存，返回指向申请内存的指针。
    2. calloc()：申请指定大小的内存，并将申请到的内存初始化为0。
    3. realloc()：调整内存大小，返回指向调整后的内存的指针。
    4. free()：释放动态内存。
注意：
    1. 申请的内存必须通过 free() 函数释放，否则会造成内存泄漏。
    2. 申请的内存大小建议大于 0，可能会导致程序崩溃。
    3. 申请的内存大小不能超过系统可分配的最大内存，否则会导致系统分配失败。
 重点：
    动态内存分布的空间的数据被存放在堆区（heap）空间。

一、malloc()函数
    申请动态内存，返回指向申请内存的指针。
    语法：void* malloc(size_t size);
    参数：
        size：要申请的内存大小，以字节为单位。
    返回值：
        成功：指向申请内存的指针。
        失败：返回 NULL。
    说明：
        ● 如果开辟成功,则返回一个指向开辟好空间的指针。
        ● 如果开辟失败,则返回一个NULL指针,因此 malloc 的返回值一定要做检查。
        ● 返回值的类型是 void*,所以 malloc 函数并不知道开辟空间的类型,
            具体在使用的时候使用者自己来决定。
        ● 如果参数 size 为 0, malloc 的行为是标准是未定义的, 取决于编译器。

二、free()函数
    释放动态内存。
    语法：void free(void* ptr);
    参数：
        ptr：指向要释放的动态内存的指针。（重要：只能指向动态申请空间的起始位置，否则可能会导致程序崩溃）
    说明：
        free函数用来释放动态开辟的内存。如果用来释放栈区或者全局变量（静态区）的内存, 会报错
            这些内存不需要调用 free 函数, 系统会自动回收。
        ● 如果参数 ptr 指向的空间不是动态开辟的, 那 free 函数的行为是未定义的。
        ● 如果参数 ptr 是 NULL 指针, 则函数什么事都不做。
    注意：
        此函数不会更改其自身的值，因此它仍然指向相同的（现在无效的）位置。
        ● 释放内存后，指针变量的值不再有效，不能再使用该指针。
        ● 释放内存后，原来申请的内存空间可能被其他程序使用，因此不能再对其进行读写操作。
        ● 释放内存后，ptr 指针地址变成未初始化的野指针，不能再次使用。
            因此，释放内存后，应该将指针置为 NULL，防止野指针。

三、calloc()函数
    申请指定大小的内存，并将申请到的内存初始化为0。
    语法：void* calloc(size_t num, size_t size);
    参数：
        num：要申请的内存块的个数。
        size：每个内存块的大小，以字节为单位。
    返回值：
        成功：返回指向申请内存的指针。
        失败：返回 NULL。
    说明：
        ● calloc 函数与 malloc 函数类似, 但 calloc 会将申请到的内存初始化为 0
        可以简单理解为 calloc = malloc + memset

四、realloc()函数
    调整动态内存大小，即改变原有内存大小，
        返回指向调整后的内存的指针。指针不一定是原来申请的内存。
    语法：void* realloc(void* ptr, size_t size);
    参数：
        ptr：指向要调整的动态内存的指针。
        size：新的内存大小，以字节为单位。
    返回值：
        成功：返回指向调整后的内存的指针。
        失败：返回 NULL。
    说明：
        ● 如果 ptr 指向 NULL 指针, 则等价于 malloc(size)
        ● 如果 size 为 0, 则等价于 free(ptr)
        ● 如果 ptr 后面有足够的内存空间, 则 realloc 不会重新分配内存, 而是返回原来的指针
        ● 如果 ptr 后面没有足够的内存空间, 则 realloc 会重新分配一块内存, 并将原来的数据拷贝过去
        ● 如果 ptr 指向原来分配的内存大于 size, 则 realloc 会重新分配一块内存, 并将原来的数据拷贝过去
        ● 如果调整后的内存空间分配失败, 则原来的内存空间不会被修改, 仍然指向原来的内存
    要注意：
        不要频繁使用 realloc 函数, 因为它会造成内存碎片,浪费内存,
            而且 realloc 函数可能需要在堆区重新计算新空间，会导致程序效率下降。
*/


// 柔性数组
/* C99 以后的版本引入了柔性数组（flexible array member）的概念，
    它允许结构体的最后一个成员是数组，而不用事先声明数组的大小。
    这种数组的大小由结构体的大小和其他成员的大小共同决定。
    柔性数组的声明形式如下：
        类型说明符 成员名[ ];
    其中，类型说明符是结构体的类型，成员名是数组的名称，数组的大小可变。
    结构体中只能有一个柔性数组成员，而且该成员必须是结构体的最后一个成员。

示例：
    struct Person {
        char name[10];
        int age;
        int p[]; // 柔性数组，指向 int 型数组的指针
        // int c[0]; 也是可以的
    };
柔性数组的特点:
    1.结构中的柔性数组成员前面必须至少一个其他成员。
    2.sizeof 返回的这种结构大小不包括柔性数组的内存。
    3.包含柔性数组成员的结构用 malloc() 函数进行内存的动态分配, 并且分配的内存应该
        大于结构的大小, 以适应柔性数组的预期大小。
柔性数组的优点:
    减少动态内存分配的次数，提高效率；其次，因为柔性数组的内存是连续的，利于减少内存碎片。

可以学习一下“C语言结构体里的成员数组和指针”这篇文章，陈皓的博客。
*/


static void test_not_free()
{
    int* p = (int*)malloc(10 * sizeof(int)); // 申请内存，注意是在堆区
    if (p == NULL)
    {
        printf("Memory allocation failed: %s\n", strerror(errno));
        return errno;
    }
    // 使用内存
    int i;
    for (i = 0; i < 10; ++i)
        printf("p[%d] = %d   ", i, p[i] = i);
    printf("\n");
    return;

    // 忘记释放内存，或者函数无法释放空间，导致内存泄漏
    /* 因为动态申请的空间是在堆区，函数执行完毕后，可能 p 指针指向的空间暂时不被释放 */
    free(p);
    p = NULL; // 置空指针，防止野指针
}


int main()
{
    // malloc() 与 free() 函数示例
    {
        print_start_line("malloc() 与 free() 函数示例开始");
        int* pi;
        int size = 10;
        pi = (int*)malloc(size * sizeof(int)); // 申请内存
        if (pi == NULL)
        {
            printf("Memory allocation failed: %s\n", strerror(errno));
            return errno;
        }
        // 使用内存
        for (int i = 0; i < size; i++)
        {
            pi[i] = i; // 赋值，即初始化
            printf("pi[%d] = %d   ", i, pi[i]);
        }
        printf("\n");
        // 释放内存
        free(pi);
        pi = NULL; // 置空指针，防止野指针
        /*while (1)
            malloc(1); // 死循环，直到内存耗尽，导致系统崩溃  */

        print_end_line("malloc() 与 free() 函数示例结束");
    }

    // calloc() 与 free() 函数示例
    {
        print_start_line("calloc() 与 free() 函数示例开始");
        char* pc = (char*)calloc(10, sizeof(char));
        if (pc == NULL)
        {
            printf("Memory allocation failed: %s\n", strerror(errno));
            return errno;
        }
        // 使用内存
        for (int i = 0; i < 10; i++)
            printf("pc[%d] = %d   ", i, (int)*(pc + i));
        printf("\n");
        // 释放内存
        free(pc);
        pc = NULL; // 置空指针，防止野指针

        print_end_line("calloc() 与 free() 函数示例结束");
    }

    // realloc() 与 free() 函数示例
    {
        print_start_line("realloc() 与 free() 函数示例开始");
        int size = 5;
        int* pii = (int*)malloc(size * sizeof(int)); // 申请内存
        if (pii == NULL)
        {
            printf("Memory allocation failed: %s\n", strerror(errno));
            return errno;
        }
        // 使用内存
        int i;
        for (i = 0; i < size; ++i)
        {
            pii[i] = i; // 赋值，即初始化
            printf("pi[%d] = %d   ", i, pii[i]);
        }
        printf("\n");
        // 调整内存大小
        int* ret_p = (int*)realloc(pii, 10 * sizeof(int)); // 调整内存大小
        if (ret_p == NULL)
        {
            printf("Memory allocation failed: %s\n", strerror(errno));
            return errno;
        }
        else
        {
            pii = ret_p; // 指针指向调整后的内存
            size += 5;
            ret_p = NULL; // 置空指针，防止野指针
        }
        // 使用调整后的内存
        for (i = 0; i < size; ++i)
        {
            pii[i] = i + 1; // 赋值，即初始化
            printf("pi[%d] = %d  ", i, pii[i]);
        }
        printf("\n");
        // 释放内存
        free(pii);
        pii = NULL; // 置空指针，防止野指针

        // 再次申请内存，这里 size = 10
        pii = (int*)malloc(size * sizeof(int)); // 申请内存
        if (pii == NULL)
        {
            printf("Memory allocation failed: %s\n", strerror(errno));
            return errno;
        }
        // 使用内存
        for (i = 0; i < size; ++i)
        {
            pii[i] = i + 1; // 赋值，即初始化
            printf("pi2[%d] = %d  ", i, pii[i]);
        }
        printf("\n");
        long long copy_pii = (long long*)pii; // 指针拷贝，强制类型转换
        //调整内存大小
        ret_p = (int*)realloc(pii, 5 * sizeof(int)); // 调整内存大小
        if (ret_p == NULL)
        {
            printf("Memory allocation failed: %s\n", strerror(errno));
            return errno;
        }
        else
        {
            pii = ret_p; // 指针指向调整后的内存
            ret_p = NULL; // 置空指针，防止野指针
        }
        // 使用调整后的内存
        for (i = 0; i < size; ++i)
            printf("pi2[%d] = %d  ", i, pii[i]); // 调整后的 pii + 5 以后的内存值不定
        printf("\n");
        for (i = 0; i < size; ++i)
            printf("pi2[%d] = %d  ", i, *(int*)(copy_pii + i * 4)); // 原来的 pii 内存值
        // 释放内存
        free(pii);
        pii = NULL; // 置空指针，防止野指针

        print_end_line("realloc() 与 free() 函数示例结束");
    }

    // 常见错误示例
    {
        print_start_line("常见错误示例开始");
        // 释放非动态内存
        int* pi = (int*)malloc(10 * sizeof(int)); // 申请内存
        if (pi == NULL)
        {
            printf("Memory allocation failed: %s\n", strerror(errno));
            return errno;
        }
        // 使用内存
        int i;
        for (i = 0; i < 10; i++)
            printf("pi[%d] = %d   ", i, *pi++ = i); // 指针自增，向后偏移
        printf("\n");
        // 释放非动态内存
        //free(pi); // 释放非动态内存，因为上面的 p 指针偏移了
        pi = NULL; // 置空指针，防止野指针
        printf("释放非动态内存示例结束\n\n");

        // 对同一块空间的多次释放
        int* p = (int*)malloc(10 * sizeof(int)); // 申请内存
        if (p == NULL)
        {
            printf("Memory allocation failed: %s\n", strerror(errno));
            return errno;
        }
        // 使用内存
        for (i = 0; i < 10; ++i)
            printf("p[%d] = %d   ", i, p[i] = i);
        printf("\n");
        // 释放内存
        free(p);
        //free(p); // 释放同一块内存两次，导致程序崩溃
        // 动态空间在不用了之后一定要记得释放，否则会造成内存泄漏
        //test_not_free();
        print_end_line("常见错误示例结束");
    }

    // 柔性数组示例
    {
        print_start_line("柔性数组示例开始");
        // 定义结构体
        typedef struct Student {
            char class[20];
            float score;
        } student;
        typedef struct Person {
            char name[10];
            student stu;
            int age;
            int p[]; // 柔性数组，指向 int 型数组的指针
            // int p[0]; 也是可以的
        } person;
        printf("sizeof(person) = %zd\n", sizeof(person)); // 40
        int size = 10;
        person* p1 = (person*)malloc(sizeof(person) + size * sizeof(int));
        if (p1 == NULL)
        {
            printf("Memory allocation failed: %s\n", strerror(errno));
            return errno;
        }
        // 使用内存
        strcpy(p1->name, "Tom");
        strcpy(p1->stu.class, "一年级b班");
        p1->stu.score = 75.5;
        p1->age = 20;
        int i;
        for (i = 0; i < size; ++i)
            p1->p[i] = i + 1;
        // 打印结构体
        printf("name = %s, age = %d, score = %.2f, class = %s\n",
            p1->name, p1->age, p1->stu.score, p1->stu.class);
        for (i = 0; i < size; ++i)
            printf("p1->p[%d] = %d\n", i, p1->p[i]);
        // 再次申请内存
        size += 10;
        person* new_p1 = (person*)realloc(p1, sizeof(person) + size * sizeof(int));
        if (!new_p1)
        {
            printf("Memory allocation failed: %s\n", strerror(errno));
            return errno;
        }
        p1 = new_p1; // 指针指向调整后的内存
        for (i = 10; i < size; ++i)
            p1->p[i] = i + 11;
        // 打印结构体柔性数组部分
        printf("新扩容后的喷p1里面的p：\n");
        for (i = 0; i < size; ++i)
            printf("p1->p[%d] = %d\n", i, p1->p[i]);
        // 释放内存
        free(p1);
        p1 = NULL; // 置空指针，防止野指针

        print_end_line("柔性数组示例结束");
    }

    // 题目与作业
    {
        print_start_line("题目与作业开始");
        // 典型错误例题
        test_problem();

        /// 最小公倍数与最大公约数之和
        int n = 39, m = 24;
        printf("%d 和 %d 的最小公倍数加最大公约数为：%zd\n", n, m, sum_div_mul(n, m));

        // 打印空心正方形
        printf_square(7);

        print_end_line("题目与作业结束");
    }

    return 0;
}

